leetcode经典例题第三题,关于求坐标共线问题。

题目描述

image

解题思路

点共线,那么最容易想到的思路就是确定斜率,斜率相同不就共线了。但是还有两点特殊情况需要考虑,二是当两个点重合时,无法确定一条直线,但这也是共线的情况,需要特殊处理。二是斜率不存在的情况,由于两个点(x1, y1)和(x2, y2)的斜率k表示为(y2 - y1) / (x2 - x1),那么当x1 = x2时斜率不存在,这种共线情况需要特殊处理。这里我对重合的情况,斜率不存在的情况以及斜率为0的情况进行了讨论,因为这比较好处理,所以处理一下斜率为0的没什么问题。最后就是通用情况,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public int maxPoints(Point[] points) {
if(points == null){
return 0;
}
if(points.length <= 2){
return points.length;
}
Map<Double,Integer> map = new HashMap<>();
int result = 0;
for(int i=0;i<points.length;i++){
map.clear();
int overlap = 0;
int vertical = 0;
int horizon = 0;
int max = 0;
double rate = 0.0;
for(int j=i+1;j<points.length;j++){
double gapx = new Double(points[i].x) - new Double(points[j].x);
double gapy = new Double(points[i].y) - new Double(points[j].y);
if(gapx == 0 && gapy == 0){
overlap++;
continue;
}else if(gapx == 0){
vertical++;
max = Math.max(max,vertical);
}else if(gapy == 0){
horizon++;
max = Math.max(max,horizon);
}else{
rate = gapy/gapx;
if(map.containsKey(rate)){
map.put(rate,map.get(rate)+1);
}else{
map.put(rate,1);
}
max = Math.max(max,map.get(rate));
}
}
result=Math.max(result, max+overlap+1);
}
return result;
}
}

虽然可以在牛客上通过,但是这个思路在leetcode上已经不行了,它给的例子是:

1
2
3
Input      [[0,0],[94911151,94911150],[94911152,94911151]]
Output 3
Expected 2

我们注意到,由于精度丢失问题,我们算出来的斜率竟然是一样的了,所以这个程序错误地认为这三个点都共线了。因此错误。那怎么办呢?

代码提交

由于通过斜率来判断共线需要用到除法,而用double表示的双精度小数在有的系统里不一定准确,为了更加精确无误的计算共线,我们应当避免除法,从而避免无线不循环小数的出现,那么怎么办呢,我们把除数和被除数都保存下来,不做除法,但是我们要让这两数分别除以它们的最大公约数,这样例如8和4,4和2,2和1,这三组商相同的数就都会存到一个映射里面,同样也能实现我们的目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
public int maxPoints(Point[] points) {
if(points == null){
return 0;
}
if(points.length <= 2){
return points.length;
}
//key为每个数组除以最大公约数后的结果,比如[8,4],[4,2],[2,1]最后都变成[2,1]存储
Map<Map<Integer,Integer>,Integer> map = new HashMap<>();
int result = 0;

for(int i=0;i<points.length;i++){
//每次循环完毕要清空map,否则会把上次统计结果带到下一次循环来
map.clear();
//重复个数,自己算重复元素,所以初始元素为1
int dup = 1;
int max = 0;
for(int j=i+1;j<points.length;j++){
//计算出两者间隔
int x = points[i].x - points[j].x;
int y = points[i].y - points[j].y;
//重合的话就将dup加一
if(x == 0 && y == 0){
dup++;
continue;
}
//计算最大公约数
int d = gcd(x, y);
Map<Integer,Integer> tmpMap = new HashMap<>();
tmpMap.put(x/d,y/d);
//次数
map.put(tmpMap, map.getOrDefault(tmpMap, 0) + 1);
//每次都将最大的放到max中,避免最后还要遍历判断map中最大次数
max = Math.max(max,map.get(tmpMap));
}
//最后的结果就是map+dup
result = Math.max(result,max+dup);
}
return result;
}

public int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
}